package cn.cytong.nk0713.utils.nutz;


import cn.cytong.nk0713.crypto.DESUtil;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.joda.time.DateTime;
import org.nutz.dao.entity.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 计算卡片的扇区数据和密钥
 */
public class CardDataUtil {
    private Logger logger = LoggerFactory.getLogger(CardDataUtil.class);
    //    public static final byte[] WORKING_KEY = Long.toHexString(7167888303879316334L).getBytes(); //cytongcn
    public static final byte[] WORKING_KEY = hexStringToByte("6379746f6e67636e"); //cytongcn

    public static final String keyAPadding = "A1A2A3A4";
    public static final String keyBPadding = "B1B2B3B4";

    /**
     * 获取全部的写卡数据
     *
     * @param customer 用户信息
     * @param card     卡片信息
     * @return 卡片数据，key为卡片的块编号，value为数据块的数据
     */
    public Map getAllData(Record customer, Record card, CustomisedNutDao nutDao) {
        Map<Integer, String> data = new HashMap<Integer, String>();
        try {
            String block12 = "0027" + card.get("cardNo")
                    + String.format("%01x", card.get("productType") == null ? 0 : (Integer) card.get("productType"))   //产品类型
                    + String.format("%01x", (Integer) card.get("cardType"))   //卡类型
                    + String.format("%02x", (Integer) card.get("ver"))   //版本
                    + String.format("%02x", (Integer) customer.get("verifyType"))   //验证标识
                    + String.format("%04x", Long.valueOf(String.valueOf(card.get("operator_id"))))
                    + "FF";

            if (card.get("startAt") == null || card.get("endAt") == null) {
                DateTime startAt = null;
                if (card.get("activeAt") != null)
                    startAt = new DateTime((Date) card.get("activeAt"));
                else
                    startAt = new DateTime();

                startAt = startAt.withTimeAtStartOfDay();
                nutDao.executeUpdate("update card set startAt = ?, endAt = ? where id = ?",
                        startAt, startAt.plusDays(Constants.Activity_Days), card.get("id"));
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd");
            Date today = new Date();
            String block13 = "01"   //状态
                    + dateFormat.format(today)   //发行日期
                    + dateFormat.format(today)    //启用日期
                    + dateFormat.format(card.get("startAt"))     //有效日期开始时间
                    + dateFormat.format(card.get("endAt"))     //有效日期结束时间
                    + "FFFFFF";   //保留
            String block14 = encodeIdNo((String) customer.get("idNo"))     //身份证
                    + encodePeopleName((String) customer.get("name"))       //用户姓名
                    + String.format("%02x", customer.get("requirement_id")) //办卡条件
                    + encodeFingerNote(customer, nutDao)        //指纹
                    + "FFFF";
            data.put(12, block12);
            data.put(13, block13);
            data.put(14, block14);

            //16,17,18扇区存储附加信息
            List<Record> appendInfoList = nutDao.queryTableRecord("CustomerAppendInfo",
                    "customer_id = ? order by tag", customer.get("id"));
            List<String> appendInfoBlocks = getAppendInfo(appendInfoList);
            data.put(16, appendInfoBlocks.get(0));
            data.put(17, appendInfoBlocks.get(1));
            data.put(18, appendInfoBlocks.get(2));

            String block20 = "00000000000000000000000000000000";
            data.put(20, block20);

            int fingerBlockIndex = 21;
            if (customer.get("finger1_id") != null) {
                Record finger = nutDao.fetchTableRecord("FingerInfo", "id = ?", customer.get("finger1_id"));
                if (finger.get("template") != null) {
                    List<String> finger1Blocks = decodeFingerTemplate((String) finger.get("template"));

                    for (String blockData : finger1Blocks) {
                        if (fingerBlockIndex % 4 == 3) {
                            fingerBlockIndex++;
                        }

                        data.put(fingerBlockIndex, blockData);
                        fingerBlockIndex++;
                    }
                }
            }

            if (customer.get("finger2_id") != null) {
                Record finger = nutDao.fetchTableRecord("FingerInfo", "id = ?", customer.get("finger2_id"));
                if (finger.get("template") != null) {
                    List<String> finger2Blocks = decodeFingerTemplate((String) finger.get("template"));

                    for (String blockData : finger2Blocks) {
                        if (fingerBlockIndex % 4 == 3) {
                            fingerBlockIndex++;
                        }

                        data.put(fingerBlockIndex, blockData);
                        fingerBlockIndex++;
                    }
                }
            }
        } catch (Exception e) {
            logger.error("error in method CardDataUtil.getAllData", e);
        }
        return data;
    }

    /**
     * 对用户附加信息按tlv格式进行编码，总长度为48字节
     *
     * @param appendInfoList 用户附加信息列表
     * @return 编码后的数据，3个长度为32的十六进制字符串
     */
    public List<String> getAppendInfo(List<Record> appendInfoList) {
        List<String> blocks = new ArrayList<String>();
        StringBuilder data = new StringBuilder();

        for (Record appendInfo : appendInfoList) {
            try {
                if (data.length() > 92) {
                    break;  //至少要放下tag和length共2个字节，否则就不继续添加附加信息了。
                }
                data.append(appendInfo.getString("tag"));

                int lenLeft = (96 - data.length()) / 2 - 1;
                String info = appendInfo.getString("info");
                while (getGBKByteLen(info) > lenLeft) {
                    info = info.substring(0, info.length() - 1);
                }

                data.append(String.format("%02x", getGBKByteLen(info)));
                data.append(bytesToHexString(info.getBytes("GBK")));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }

        //3个数据块一共48字节
        while (data.length() < 96) {
            data.append("FF");
        }

        blocks.add(data.substring(0, 32));
        blocks.add(data.substring(32, 64));
        blocks.add(data.substring(64, 96));

        return blocks;
    }

    /**
     * 将字符串转换为GBK的byte数组，返回数组长度
     *
     * @param str 字符串
     * @return GBK编码的实际字节长度
     */
    public static int getGBKByteLen(String str) {
        try {
            return str.getBytes("GBK").length;
        } catch (UnsupportedEncodingException e) {
            return 0;
        }
    }

    /**
     * 根据物理卡号计算扇区密钥
     *
     * @param cardId 物理卡号
     * @return 扇区密钥，键为keyA和keyB
     */
    public Map getSectorKey(String cardId) {
        Validate.notNull(cardId);
        Validate.isTrue(cardId.length() == 8, "卡号长度错误！");

        Map<String, String> keys = new HashMap<String, String>();

        //计算扇区密钥
        String keyA = bytesToHexString(DESUtil.des_crypt(WORKING_KEY, hexStringToBytes(cardId + keyAPadding)));
        String keyB = bytesToHexString(DESUtil.des_crypt(WORKING_KEY, hexStringToBytes(cardId + keyBPadding)));

        keyA = keyA.substring(0, 12);
        keyB = keyB.substring(0, 12);
        keys.put("keyA", keyA);
        keys.put("keyB", keyB);

        System.out.println("keyA : " + keyA);
        System.out.println("keyB : " + keyB);

        return keys;
    }

    /**
     * 对身份证号进行编码，身份证的前6位和后4位，并将X转换为A
     *
     * @param idNo 用户身份证号
     * @return 10位十六进制字符串
     */
    public static String encodeIdNo(String idNo) {
        if (idNo == null || idNo.length() == 0) {
            return "FFFFFFFFFF";
        }

        idNo = idNo.replace('X', 'A');
        while (idNo.length() < 10) {
            idNo += "F";
        }

        return idNo.substring(0, 6) + idNo.substring(idNo.length() - 4);
    }

    /**
     * 对用户姓名编码
     *
     * @param name 用户姓名
     * @return 编码后的十六进制字符串，长度为12（代表6个字节，3个汉字）
     */
    public String encodePeopleName(String name) {
        StringBuilder rtn = new StringBuilder();

        if (StringUtils.isBlank(name)) {
            name = "未知";
        }

        final int MaxNameBytesLen = 12;
        try {
            for (int i = 0; i < name.length(); i++) {
                String aChar = name.substring(i, i + 1);
                String hexStr = bytesToHexString(aChar.getBytes("GBK"));

                if (rtn.length() + hexStr.length() > MaxNameBytesLen) break;
                rtn.append(hexStr);
            }

            while (rtn.length() < 12) {
                rtn.append("00");
            }
        } catch (Exception e) {
            logger.error("error in method encodePeopleName", e);
        }

        return rtn.toString();
    }

    private String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 对用户指纹提示信息进行编码，两枚指纹，分别采集于哪只手的哪个手指
     *
     * @param customer 用户信息
     * @return
     */
    public static String encodeFingerNote(Record customer, CustomisedNutDao nutDao) {
        StringBuilder note = new StringBuilder();
        if (customer.get("finger1_id") != null) {
            Record finger1 = nutDao.fetchTableRecord("FingerInfo", "id = ?", customer.get("finger1_id"));
            note.append(finger1.get("hand"))
                    .append(finger1.get("finger"));
        } else {
            note.append("00");
        }

        if (customer.get("finger2_id") != null) {
            Record finger2 = nutDao.fetchTableRecord("FingerInfo", "id = ?", customer.get("finger2_id"));
            note.append(finger2.get("hand"))
                    .append(finger2.get("finger"));
        } else {
            note.append("00");
        }

        return note.toString();
    }

    /**
     * 对指纹信息进行解码，数据库中存储的指纹信息是发卡器直接返回的，存到卡上时需要先解码。
     * 发卡器将指纹模板数据的每半个字节都加0x30，这样就全部转成可见字符了，这里解码时要做相反的操作。
     *
     * @param template 数据库中的指纹模板数据
     * @return 数据列表，每个元素都是长度为32的十六进制字符串，对应卡上的一个块
     */
    public static List<String> decodeFingerTemplate(String template) {
        List<String> blocks = new ArrayList<String>();

        byte[] bytes = template.getBytes();
        StringBuilder builder = new StringBuilder();
        for (byte aByte : bytes) {
            aByte = (byte) (aByte - 0x30);
            builder.append(String.format("%x", aByte));
        }

        for (int i = 0; i < 16; i++) {
            blocks.add(builder.substring(i * 32, (i + 1) * 32));
        }

        return blocks;
    }

    private byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    private byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public static byte[] hexStringToByte(String hexString) {
        try {
            return Hex.decodeHex(hexString.toCharArray());
        } catch (DecoderException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        CardDataUtil util = new CardDataUtil();
        System.out.println(util.encodePeopleName("余梦a琪"));
    }
}
